<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3D地图下坠效果</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #1a2a6c, #2a3c7a);
            color: white;
            overflow: hidden;
            height: 100vh;
        }

        #container {
            position: relative;
            width: 100%;
            height: 100vh;
        }

        #map-container {
            width: 100%;
            height: 100%;
            position: absolute;
            top: 0;
            left: 0;
        }

        #ui {
            position: absolute;
            top: 20px;
            left: 20px;
            z-index: 100;
            background: rgba(0, 0, 0, 0.7);
            padding: 20px;
            border-radius: 10px;
            max-width: 350px;
            backdrop-filter: blur(5px);
            border: 1px solid rgba(255, 255, 255, 0.1);
        }

        h1 {
            margin-bottom: 15px;
            font-size: 24px;
            color: #4fc3f7;
            text-shadow: 0 0 10px rgba(79, 195, 247, 0.5);
        }

        p {
            margin-bottom: 15px;
            line-height: 1.5;
            font-size: 14px;
            color: #e0e0e0;
        }

        .control-group {
            margin: 15px 0;
        }

        label {
            display: block;
            margin-bottom: 5px;
            font-size: 14px;
            color: #bbdefb;
        }

        button {
            background: linear-gradient(to right, #2196f3, #21cbf3);
            border: none;
            color: white;
            padding: 10px 15px;
            border-radius: 5px;
            cursor: pointer;
            font-weight: bold;
            margin-right: 10px;
            margin-bottom: 10px;
            transition: all 0.3s;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
        }

        button:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 8px rgba(0, 0, 0, 0.2);
        }

        button:active {
            transform: translateY(1px);
        }

        select, input {
            width: 100%;
            padding: 8px;
            border-radius: 5px;
            border: 1px solid #444;
            background: rgba(30, 30, 40, 0.8);
            color: white;
            margin-bottom: 10px;
        }

        .status {
            margin-top: 15px;
            padding: 10px;
            background: rgba(0, 0, 0, 0.5);
            border-radius: 5px;
            font-size: 13px;
        }

        .highlight {
            color: #4fc3f7;
            font-weight: bold;
        }

        .info-box {
            position: absolute;
            bottom: 20px;
            right: 20px;
            background: rgba(0, 0, 0, 0.7);
            padding: 15px;
            border-radius: 10px;
            max-width: 300px;
            z-index: 100;
            backdrop-filter: blur(5px);
            border: 1px solid rgba(255, 255, 255, 0.1);
        }

        .info-box h3 {
            color: #4fc3f7;
            margin-bottom: 10px;
        }

        .info-box ul {
            padding-left: 20px;
        }

        .info-box li {
            margin-bottom: 8px;
            font-size: 13px;
        }

        .loading {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            text-align: center;
            z-index: 1000;
        }

        .spinner {
            border: 4px solid rgba(255, 255, 255, 0.3);
            border-radius: 50%;
            border-top: 4px solid #4fc3f7;
            width: 40px;
            height: 40px;
            animation: spin 1s linear infinite;
            margin: 0 auto 15px;
        }

        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
    </style>
</head>
<body>
<div id="container">
    <div id="map-container"></div>

    <div id="ui">
        <h1>3D地图下坠效果</h1>
        <p>体验从高空俯瞰到地面细节的逼真下坠动画。使用Three.js创建的3D地形地图。</p>

        <div class="control-group">
            <label for="drop-speed">下坠速度:</label>
            <select id="drop-speed">
                <option value="slow">缓慢下坠</option>
                <option value="medium" selected>中等速度</option>
                <option value="fast">快速下坠</option>
            </select>

            <label for="terrain-type">地形类型:</label>
            <select id="terrain-type">
                <option value="mountains" selected>山脉地形</option>
                <option value="hills">丘陵地形</option>
                <option value="plains">平原地形</option>
            </select>
        </div>

        <div class="button-group">
            <button id="start-drop">开始下坠</button>
            <button id="reset-view">重置视角</button>
            <button id="toggle-rotation">暂停旋转</button>
        </div>

        <div class="status">
            当前状态: <span class="highlight" id="status-text">准备中</span><br>
            相机高度: <span class="highlight" id="height-text">1000m</span><br>
            下坠进度: <span class="highlight" id="progress-text">0%</span>
        </div>
    </div>

    <div class="info-box">
        <h3>操作说明</h3>
        <ul>
            <li>点击"开始下坠"体验3D下坠效果</li>
            <li>鼠标拖拽可旋转视角</li>
            <li>鼠标滚轮可缩放视图</li>
            <li>使用下拉菜单调整下坠速度和地形</li>
            <li>点击"重置视角"回到初始位置</li>
        </ul>
    </div>

    <div class="loading" id="loading">
        <div class="spinner"></div>
        <p>加载3D引擎中...</p>
    </div>
</div>

<script>
    // 手动实现简化的OrbitControls
    class SimpleOrbitControls {
        constructor(camera, domElement) {
            this.camera = camera;
            this.domElement = domElement;
            this.enabled = true;
            this.enableDamping = true;
            this.dampingFactor = 0.05;
            this.minDistance = 100;
            this.maxDistance = 2000;
            this.maxPolarAngle = Math.PI / 2;

            this.target = new THREE.Vector3();
            this.spherical = new THREE.Spherical();
            this.sphericalDelta = new THREE.Spherical();

            this.scale = 1;
            this.rotateStart = new THREE.Vector2();
            this.rotateEnd = new THREE.Vector2();
            this.rotateDelta = new THREE.Vector2();

            this.panStart = new THREE.Vector2();
            this.panEnd = new THREE.Vector2();
            this.panDelta = new THREE.Vector2();

            this.state = -1; // -1: none, 0: rotate, 1: pan

            this.bindEvents();
            this.update();
        }

        bindEvents() {
            this.domElement.addEventListener('mousedown', this.onMouseDown.bind(this));
            this.domElement.addEventListener('wheel', this.onMouseWheel.bind(this));
            document.addEventListener('mousemove', this.onMouseMove.bind(this));
            document.addEventListener('mouseup', this.onMouseUp.bind(this));
        }

        onMouseDown(event) {
            event.preventDefault();

            if (!this.enabled) return;

            switch (event.button) {
                case 0: // left button
                    this.state = 0;
                    this.rotateStart.set(event.clientX, event.clientY);
                    break;
                case 2: // right button
                    this.state = 1;
                    this.panStart.set(event.clientX, event.clientY);
                    break;
            }
        }

        onMouseMove(event) {
            if (!this.enabled) return;

            switch (this.state) {
                case 0: // rotate
                    this.rotateEnd.set(event.clientX, event.clientY);
                    this.rotateDelta.subVectors(this.rotateEnd, this.rotateStart).multiplyScalar(0.5);
                    this.rotateStart.copy(this.rotateEnd);
                    this.rotate();
                    break;
                case 1: // pan
                    this.panEnd.set(event.clientX, event.clientY);
                    this.panDelta.subVectors(this.panEnd, this.panStart).multiplyScalar(0.5);
                    this.panStart.copy(this.panEnd);
                    this.pan();
                    break;
            }
        }

        onMouseUp() {
            this.state = -1;
        }

        onMouseWheel(event) {
            if (!this.enabled) return;

            event.preventDefault();

            if (event.deltaY < 0) {
                this.scale /= 1.1;
            } else {
                this.scale *= 1.1;
            }

            this.zoom();
        }

        rotate() {
            const element = this.domElement;
            this.sphericalDelta.theta -= 2 * Math.PI * this.rotateDelta.x / element.clientHeight;
            this.sphericalDelta.phi -= 2 * Math.PI * this.rotateDelta.y / element.clientHeight;
        }

        pan() {
            const offset = new THREE.Vector3();
            offset.copy(this.camera.position).sub(this.target);
            let targetDistance = offset.length();
            targetDistance *= Math.tan((this.camera.fov / 2) * Math.PI / 180.0);

            this.panLeft(2 * this.panDelta.x * targetDistance / this.domElement.clientHeight, this.camera.matrix);
            this.panUp(2 * this.panDelta.y * targetDistance / this.domElement.clientHeight, this.camera.matrix);
        }

        panLeft(distance, matrix) {
            const v = new THREE.Vector3();
            v.setFromMatrixColumn(matrix, 0);
            v.multiplyScalar(-distance);
            this.target.add(v);
        }

        panUp(distance, matrix) {
            const v = new THREE.Vector3();
            v.setFromMatrixColumn(matrix, 1);
            v.multiplyScalar(distance);
            this.target.add(v);
        }

        zoom() {
            const offset = new THREE.Vector3();
            offset.copy(this.camera.position).sub(this.target);
            let scaleFactor = this.scale;
            const minDistance = this.minDistance;
            const maxDistance = this.maxDistance;

            if (scaleFactor !== 1) {
                const distance = offset.length() * scaleFactor;
                if (distance < minDistance) {
                    scaleFactor = minDistance / offset.length();
                } else if (distance > maxDistance) {
                    scaleFactor = maxDistance / offset.length();
                }

                offset.multiplyScalar(scaleFactor);
            }

            this.camera.position.copy(this.target).add(offset);
        }

        update() {
            const offset = new THREE.Vector3();
            offset.copy(this.camera.position).sub(this.target);

            // Rotate
            if (this.sphericalDelta.theta !== 0 || this.sphericalDelta.phi !== 0) {
                this.spherical.setFromVector3(offset);

                this.spherical.theta += this.sphericalDelta.theta;
                this.spherical.phi += this.sphericalDelta.phi;
                this.spherical.phi = Math.max(0.1, Math.min(Math.PI - 0.1, this.spherical.phi));

                this.spherical.makeSafe();

                offset.setFromSpherical(this.spherical);
                this.camera.position.copy(this.target).add(offset);
                this.camera.lookAt(this.target);

                if (this.enableDamping) {
                    this.sphericalDelta.theta *= (1 - this.dampingFactor);
                    this.sphericalDelta.phi *= (1 - this.dampingFactor);
                } else {
                    this.sphericalDelta.set(0, 0, 0);
                }
            }

            // Zoom
            if (this.scale !== 1) {
                this.zoom();
                this.scale = 1;
            }

            this.camera.lookAt(this.target);
        }
    }

    // 主应用程序
    class MapDropApp {
        constructor() {
            this.scene = null;
            this.camera = null;
            this.renderer = null;
            this.controls = null;
            this.terrain = null;
            this.isDropping = false;
            this.isRotating = true;
            this.dropProgress = 0;
            this.startHeight = 1000;
            this.currentHeight = this.startHeight;
            this.dropSpeed = 1;

            this.init();
        }

        init() {
            try {
                // 隐藏加载提示
                document.getElementById('loading').style.display = 'none';

                // 创建场景
                this.scene = new THREE.Scene();
                this.scene.background = new THREE.Color(0x87CEEB);
                this.scene.fog = new THREE.Fog(0x87CEEB, 500, 2000);

                // 创建相机
                this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 3000);
                this.resetCamera();

                // 创建渲染器
                this.renderer = new THREE.WebGLRenderer({ antialias: true });
                this.renderer.setSize(window.innerWidth, window.innerHeight);
                this.renderer.shadowMap.enabled = true;
                this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
                document.getElementById('map-container').appendChild(this.renderer.domElement);

                // 添加简化的轨道控制
                this.controls = new SimpleOrbitControls(this.camera, this.renderer.domElement);

                // 添加光源
                this.addLighting();

                // 创建地形
                this.createTerrain();

                // 设置事件监听器
                this.setupEventListeners();

                // 开始动画循环
                this.animate();

                // 窗口大小调整事件
                window.addEventListener('resize', () => this.onWindowResize());

                console.log('3D地图应用初始化成功');
            } catch (error) {
                console.error('初始化失败:', error);
                document.getElementById('loading').innerHTML = '<p style="color: red;">加载失败，请刷新页面重试</p>';
            }
        }

        addLighting() {
            // 环境光
            const ambientLight = new THREE.AmbientLight(0x404040, 0.4);
            this.scene.add(ambientLight);

            // 方向光（太阳）
            const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
            directionalLight.position.set(100, 300, 200);
            directionalLight.castShadow = true;
            this.scene.add(directionalLight);

            // 辅助光
            const hemisphereLight = new THREE.HemisphereLight(0x87CEEB, 0x8B4513, 0.3);
            this.scene.add(hemisphereLight);
        }

        createTerrain() {
            const terrainType = document.getElementById('terrain-type').value;

            // 创建地形几何体
            const geometry = new THREE.PlaneGeometry(1000, 1000, 100, 100);
            geometry.rotateX(-Math.PI / 2);

            // 根据地形类型设置高度
            const vertices = geometry.attributes.position.array;
            for (let i = 0; i < vertices.length; i += 3) {
                const x = vertices[i];
                const z = vertices[i + 2];

                let height = 0;

                switch(terrainType) {
                    case 'mountains':
                        height = 50 * (
                            Math.sin(x * 0.01) * Math.cos(z * 0.008) +
                            Math.sin(x * 0.02) * Math.cos(z * 0.015) * 0.5 +
                            Math.sin(x * 0.05) * Math.cos(z * 0.03) * 0.2
                        );
                        break;
                    case 'hills':
                        height = 20 * (
                            Math.sin(x * 0.005) * Math.cos(z * 0.005) +
                            Math.sin(x * 0.01) * Math.cos(z * 0.01) * 0.5
                        );
                        break;
                    case 'plains':
                        height = 5 * Math.sin(x * 0.002) * Math.cos(z * 0.002);
                        break;
                }

                vertices[i + 1] = height;
            }

            geometry.computeVertexNormals();

            // 创建材质 - 使用颜色而不是纹理
            const material = new THREE.MeshLambertMaterial({
                color: 0x4CAF50,
                flatShading: false
            });

            // 创建网格
            this.terrain = new THREE.Mesh(geometry, material);
            this.terrain.receiveShadow = true;
            this.scene.add(this.terrain);

            // 添加网格辅助线
            const gridHelper = new THREE.GridHelper(1000, 20, 0x444444, 0x222222);
            this.scene.add(gridHelper);
        }

        resetCamera() {
            this.camera.position.set(0, this.startHeight, 500);
            this.camera.lookAt(0, 0, 0);
            this.currentHeight = this.startHeight;
            this.dropProgress = 0;
            this.updateStatus();
        }

        startDrop() {
            if (this.isDropping) return;

            this.isDropping = true;
            this.dropProgress = 0;

            // 根据选择设置速度
            const speedSelect = document.getElementById('drop-speed');
            switch(speedSelect.value) {
                case 'slow': this.dropSpeed = 0.5; break;
                case 'medium': this.dropSpeed = 1; break;
                case 'fast': this.dropSpeed = 2; break;
            }

            document.getElementById('status-text').textContent = '下坠中';
        }

        updateDrop() {
            if (!this.isDropping) return;

            // 更新下坠进度
            this.dropProgress += 0.005 * this.dropSpeed;

            if (this.dropProgress >= 1) {
                this.dropProgress = 1;
                this.isDropping = false;
                document.getElementById('status-text').textContent = '下坠完成';
            }

            // 计算当前高度 - 使用缓动函数使下坠更自然
            const easeOutCubic = t => 1 - Math.pow(1 - t, 3);
            const easedProgress = easeOutCubic(this.dropProgress);

            this.currentHeight = this.startHeight * (1 - easedProgress) + 50;

            // 更新相机位置
            this.camera.position.y = this.currentHeight;

            // 在下坠过程中调整视角
            if (this.dropProgress < 0.7) {
                this.camera.lookAt(0, 0, 0);
            }

            this.updateStatus();
        }

        updateStatus() {
            document.getElementById('height-text').textContent = Math.round(this.currentHeight) + 'm';
            document.getElementById('progress-text').textContent = Math.round(this.dropProgress * 100) + '%';
        }

        animate() {
            requestAnimationFrame(() => this.animate());

            // 更新下坠动画
            this.updateDrop();

            // 自动旋转
            if (this.isRotating && !this.isDropping) {
                this.terrain.rotation.y += 0.002;
            }

            // 更新控件
            if (this.controls) {
                this.controls.update();
            }

            // 渲染场景
            this.renderer.render(this.scene, this.camera);
        }

        onWindowResize() {
            this.camera.aspect = window.innerWidth / window.innerHeight;
            this.camera.updateProjectionMatrix();
            this.renderer.setSize(window.innerWidth, window.innerHeight);
        }

        setupEventListeners() {
            document.getElementById('start-drop').addEventListener('click', () => this.startDrop());

            document.getElementById('reset-view').addEventListener('click', () => {
                this.resetCamera();
                document.getElementById('status-text').textContent = '已重置';
            });

            document.getElementById('toggle-rotation').addEventListener('click', () => {
                this.isRotating = !this.isRotating;
                document.getElementById('toggle-rotation').textContent =
                    this.isRotating ? '暂停旋转' : '开始旋转';
            });

            document.getElementById('terrain-type').addEventListener('change', () => {
                // 移除旧地形
                if (this.terrain) {
                    this.scene.remove(this.terrain);
                }
                // 创建新地形
                this.createTerrain();
            });
        }
    }

    // 初始化应用
    window.addEventListener('DOMContentLoaded', () => {
        // 检查Three.js是否加载成功
        if (typeof THREE === 'undefined') {
            document.getElementById('loading').innerHTML = '<p style="color: red;">Three.js加载失败，请检查网络连接</p>';
            return;
        }

        new MapDropApp();
    });
</script>
</body>
</html>
